package org.smarthings.things.management.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.event.EventAdmin;
import org.smarthings.things.management.Thing;
import org.smarthings.things.management.ThingCollection;
import org.smarthings.things.management.ThingRegistration;
import org.smarthings.things.management.ThingsRegistry;
import org.smarthings.things.management.metadata.provider.ThingActionMetadataProvider;
import org.smarthings.things.management.metadata.provider.ThingPropertyMetadataProvider;

//TODO synchronizations 

@Component(immediate = true)
@Service(ThingsRegistry.class)
public class ThingsManager implements ThingsRegistry {

	@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MANDATORY_UNARY)
	private EventAdmin eventAdmin;

	@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MANDATORY_UNARY)
	private ThingPropertyMetadataProvider thingsPropertyMetadataProvider;

	@Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.MANDATORY_UNARY)
	private ThingActionMetadataProvider thingsActionMetadataProvider;

	private ThingsEventPropagator thingsEventPropagator;
	private Map<String, Thing> things = new HashMap<String, Thing>(20);

	public void bindEventAdmin(EventAdmin eventAdmin) {
		this.eventAdmin = eventAdmin;
		this.thingsEventPropagator = new ThingsEventPropagator(eventAdmin);
	}

	public void unbindEventAdmin(EventAdmin eventAdmin) {
		this.eventAdmin = null;
		this.thingsEventPropagator = null;
	}

	public void bindThingsPropertyMetadataProvider(
			ThingPropertyMetadataProvider thingsPropertyMetadataProvider) {
		this.thingsPropertyMetadataProvider = thingsPropertyMetadataProvider;
	}

	public void unbindThingsPropertyMetadataProvider(
			ThingPropertyMetadataProvider thingsPropertyMetadataProvider) {
		this.thingsPropertyMetadataProvider = null;
	}

	public void bindThingsActionMetadataProvider(
			ThingActionMetadataProvider thingsActionMetadataProvider) {
		this.thingsActionMetadataProvider = thingsActionMetadataProvider;
	}

	public void unbindThingsActionMetadataProvider(
			ThingActionMetadataProvider thingsActionMetadataProvider) {
		this.thingsActionMetadataProvider = null;
	}

	@Override
	public Thing registerThing(ThingRegistration registration) {
		Thing thing = ThingBuilder
				.create(thingsEventPropagator, thingsPropertyMetadataProvider,
						thingsActionMetadataProvider).name(registration.getName())
				.tags(registration.getTags()).properties(registration.getProperties())
				.actions(registration.getActions()).build();
		things.put(thing.getUUID(), thing); // TODO store persistently
		thingsEventPropagator.fireThingRegisteredEvent(thing);
		return thing;
	}

	@Override
	public Thing getThing(String thingUUID) {
		return things.get(thingUUID);
	}

	@Override
	public Thing getThingByName(String name) {
		for (Thing thing : things.values()) {
			if (name.equals(thing.getName())) {
				return thing;
			}
		}
		return null;
	}

	@Override
	public Thing getThingByTag(String tag) {
		for (Thing thing : things.values()) {
			if (contains(tag, thing.getTags())) {
				return thing;
			}
		}
		return null;
	}

	@Override
	public ThingCollection listThings() {
		return new ThingCollectionImpl(new ArrayList<Thing>(things.values()));
	}

	@Override
	public ThingCollection filterThings(String filter) {
		return new ThingCollectionImpl(new ArrayList<Thing>(things.values())); // TODO parse filter
	}

	@Override
	public void unregisterThing(String thingUUID) {
		Thing thing = things.remove(thingUUID);
		thingsEventPropagator.fireThingUnregisteredEvent(thing);
	}

	private static boolean contains(Object element, Object[] elements) {
		if (elements == null) {
			return false;
		}
		for (Object next : elements) {
			if (next.equals(element)) {
				return true;
			}
		}
		return false;
	}
}
